<?php
/********************************************************************
Product    : Plotalot
Date       : 28 September 2011
Copyright  : Les Arbres Design 2010-2011
Contact    : http://extensions.lesarbresdesign.info
Licence    : GNU General Public License
*********************************************************************/

defined('_JEXEC') or die('Restricted Access');

define("PLOTALOT_VERSION","2.05");
define("PLOTALOT_COMPONENT", "com_plotalot");

define("GOOGLE_CODE_TABLE", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.");
define("GOOGLE_MAX_URL", 1990);
define("GOOGLE_MAX_PIXELS", 300000);
define("GOOGLE_MAX_XY", 1000);
define("CHART_MAX_PLOTS", 20);
define("PLOTALOT_TRACE_FILE",JPATH_COMPONENT.DS."plotalot.txt");
define("COL_X", 0);
define("COL_Y", 1);
define("COL_Z", 2);

define("CHART_TYPE_ANY", 0);
define("CHART_TYPE_TABLE", 1);
define("CHART_TYPE_SINGLE_ITEM",10);
define("CHART_TYPE_MAX_TABLE",50);		// not a real chart type
define("CHART_TYPE_LINE", 100);
define("CHART_TYPE_SCATTER", 200);
define("CHART_TYPE_BAR", 299);			// not a real chart type
define("CHART_TYPE_BAR_H_STACK", 300);
define("CHART_TYPE_BAR_H_GROUP", 310);
define("CHART_TYPE_BAR_V_STACK", 320);
define("CHART_TYPE_BAR_V_GROUP", 330);
define("CHART_TYPE_PIE", 399);			// not a real chart type
define("CHART_TYPE_PIE_2D", 400);
define("CHART_TYPE_PIE_3D", 410);
define("CHART_TYPE_PIE_2D_V", 420);
define("CHART_TYPE_PIE_3D_V", 430);

define("FORMAT_NONE",0);
define("FORMAT_NUM_UK_0", 10);
define("FORMAT_NUM_UK_1", 20);
define("FORMAT_NUM_UK_2", 30);
define("FORMAT_NUM_FR_0", 40);
define("FORMAT_NUM_FR_1", 50);
define("FORMAT_NUM_FR_2", 60);
define("FORMAT_DATE_DMY",100);
define("FORMAT_DATE_MDY",120);
define("FORMAT_DATE_DMONY",125);
define("FORMAT_DATE_DM",130);
define("FORMAT_DATE_MD",140);
define("FORMAT_DATE_MY",144);
define("FORMAT_DATE_MONY",145);
define("FORMAT_DATE_Y",150);
define("FORMAT_DATE_M",160);
define("FORMAT_DATE_MON",161);
define("FORMAT_DATE_MONTH",162);
define("FORMAT_DATE_D",170);
define("FORMAT_DATE_DAY",180);
define("FORMAT_TIME_HHMM",190);
define("FORMAT_TIME_HHMMSS",195);
define("FORMAT_TIME_HH",200);
define("FORMAT_TIME_MM",210);
define("FORMAT_PERCENT_0", 300);
define("FORMAT_PERCENT_1", 310);
define("FORMAT_PERCENT_2", 320);
define("FORMAT_ORDERED",900);

define("BACKGROUND_NONE", 0);
define("BACKGROUND_COLOUR", 1);
define("BACKGROUND_SEMI_TRANSPARENT", 50);
define("BACKGROUND_TRANSPARENT", 100);
define("BACKGROUND_H_GRADIENT", 200);
define("BACKGROUND_V_GRADIENT", 300);
define("BACKGROUND_D_GRADIENT", 400);
define("BACKGROUND_H_STRIPE", 500);
define("BACKGROUND_V_STRIPE", 600);

define("LINE_NORMAL", 0);
define("LINE_DASH", 10);
define("LINE_THIN_SOLID", 20);
define("LINE_THIN_DASH", 30);
define("LINE_THICK_SOLID", 40);
define("LINE_THICK_DASH", 50);
define("PIE_LIGHT_GRADIENT", 60);
define("PIE_DARK_GRADIENT", 70);
define("PIE_MULTI_COLOUR", 80);

define("LEGEND_NONE", 0);
define("LEGEND_LEFT", 10);
define("LEGEND_RIGHT", 20);
define("LEGEND_TOP", 30);
define("LEGEND_BOTTOM", 40);

if (class_exists("Plotalot"))
	return;

class Plotalot
{
var $error = '';				// error message to be returned
var $warning = '';				// warning to be returned
var $chart_url = '';			// the chart url (http://chart.apis.google.com/chart?...)
var $chart_data = null;			// the chart data definition structure
var $title = '';				// the resolved chart title
var $datasets = array();		// the dataset arrays
var $total_rows = 0;			// total number of rows from all queries
var $active_plots = 0;			// number of plots that have rows
var $chart_title = '';			// chart title
var $x_title = '';				// X axis title
var $y_title = '';				// Y axis title
var $chart_x_min;				// overall minimum X value for all datasets
var $chart_x_max;				// overall maximum X value for all datasets
var $chart_y_min;				// overall minimum Y value for all datasets
var $chart_y_max;				// overall maximum Y value for all datasets

function Plotalot()
{
	$this->dataFormats = array(
		FORMAT_NONE      =>  JText::_('COM_PLOTALOT_NONE'), 
		FORMAT_NUM_UK_0  =>  "99,999",
		FORMAT_NUM_UK_1  =>  "99,999.9",
		FORMAT_NUM_UK_2  =>  "99,999.99",
		FORMAT_NUM_FR_0  =>  "99 999",
		FORMAT_NUM_FR_1  =>  "99 999,9",
		FORMAT_NUM_FR_2  =>  "99 999,99",
		FORMAT_DATE_DMY  =>  JText::_('COM_PLOTALOT_FORMAT_DATE_DMY'), 
		FORMAT_DATE_MDY  =>  JText::_('COM_PLOTALOT_FORMAT_DATE_MDY'),
		FORMAT_DATE_DMONY=>  JText::_('COM_PLOTALOT_FORMAT_DATE_DMONY'),
		FORMAT_DATE_DM   =>  JText::_('COM_PLOTALOT_FORMAT_DATE_DM'), 
		FORMAT_DATE_MD   =>  JText::_('COM_PLOTALOT_FORMAT_DATE_MD'),
		FORMAT_DATE_MY   =>  JText::_('COM_PLOTALOT_FORMAT_DATE_MY'),
		FORMAT_DATE_MONY =>  JText::_('COM_PLOTALOT_FORMAT_DATE_MONY'),
		FORMAT_DATE_Y    =>  JText::_('COM_PLOTALOT_FORMAT_DATE_Y'),
		FORMAT_DATE_M    =>  JText::_('COM_PLOTALOT_FORMAT_DATE_M'),
		FORMAT_DATE_MON  =>  JText::_('COM_PLOTALOT_FORMAT_DATE_MON'),
		FORMAT_DATE_MONTH => JText::_('COM_PLOTALOT_FORMAT_DATE_MONTH'),
		FORMAT_DATE_D    =>  JText::_('COM_PLOTALOT_FORMAT_DATE_D'), 
		FORMAT_DATE_DAY  =>  JText::_('COM_PLOTALOT_FORMAT_DATE_DAY'),
		FORMAT_TIME_HHMM =>  JText::_('COM_PLOTALOT_FORMAT_TIME_HHMM'),
		FORMAT_TIME_HHMMSS => JText::_('COM_PLOTALOT_FORMAT_TIME_HHMMSS'),
		FORMAT_TIME_HH   =>  JText::_('COM_PLOTALOT_FORMAT_TIME_HH'),
		FORMAT_TIME_MM   =>  JText::_('COM_PLOTALOT_FORMAT_TIME_MM'),
		FORMAT_PERCENT_0 =>  JText::_('COM_PLOTALOT_FORMAT_PERCENT_0'),
		FORMAT_PERCENT_1 =>  JText::_('COM_PLOTALOT_FORMAT_PERCENT_1'),
		FORMAT_PERCENT_2 =>  JText::_('COM_PLOTALOT_FORMAT_PERCENT_2'),
		FORMAT_ORDERED   =>  JText::_('COM_PLOTALOT_FORMAT_ORDERED'));
		
	$this->chart_types = array(
		CHART_TYPE_ANY         => JText::_('COM_PLOTALOT_CHART_TYPE_ANY'), 
		CHART_TYPE_TABLE       => JText::_('COM_PLOTALOT_CHART_TYPE_TABLE'),
		CHART_TYPE_SINGLE_ITEM => JText::_('COM_PLOTALOT_CHART_TYPE_SINGLE_ITEM'),
		CHART_TYPE_LINE        => JText::_('COM_PLOTALOT_CHART_TYPE_LINE'),
		CHART_TYPE_SCATTER     => JText::_('COM_PLOTALOT_CHART_TYPE_SCATTER'),
		"OPTGROUP_START_1"     => JText::_('COM_PLOTALOT_CHART_TYPE_BAR'), 
		CHART_TYPE_BAR_H_STACK => JText::_('COM_PLOTALOT_CHART_TYPE_BAR_H_STACK'),
		CHART_TYPE_BAR_H_GROUP => JText::_('COM_PLOTALOT_CHART_TYPE_BAR_H_GROUP'),
		CHART_TYPE_BAR_V_STACK => JText::_('COM_PLOTALOT_CHART_TYPE_BAR_V_STACK'),
		CHART_TYPE_BAR_V_GROUP => JText::_('COM_PLOTALOT_CHART_TYPE_BAR_V_GROUP'),
		"OPTGROUP_END_1"       => '',
		"OPTGROUP_START_2"     => JText::_('COM_PLOTALOT_CHART_TYPE_PIE'), 
		CHART_TYPE_PIE_2D      => JText::_('COM_PLOTALOT_CHART_TYPE_PIE_2D'),
		CHART_TYPE_PIE_3D      => JText::_('COM_PLOTALOT_CHART_TYPE_PIE_3D'),
		CHART_TYPE_PIE_2D_V    => JText::_('COM_PLOTALOT_CHART_TYPE_PIE_2D_V'),
		CHART_TYPE_PIE_3D_V    => JText::_('COM_PLOTALOT_CHART_TYPE_PIE_3D_V'),
		"OPTGROUP_END_2"       => ''
		);
		
	$this->lineStyles = array(
		0                   =>  JText::_('COM_PLOTALOT_NONE'), 
		"OPTGROUP_START_1"  =>  JText::_('COM_PLOTALOT_LINE_CHARTS'), 
		  LINE_NORMAL       =>  JText::_('COM_PLOTALOT_LINE_NORMAL'), 
	  	  LINE_DASH         =>  JText::_('COM_PLOTALOT_LINE_DASH'),
		  LINE_THIN_SOLID   =>  JText::_('COM_PLOTALOT_LINE_THIN_SOLID'),
		  LINE_THIN_DASH    =>  JText::_('COM_PLOTALOT_LINE_THIN_DASH'),
		  LINE_THICK_SOLID  =>  JText::_('COM_PLOTALOT_LINE_THICK_SOLID'),
		  LINE_THICK_DASH   =>  JText::_('COM_PLOTALOT_LINE_THICK_DASH'),
		"OPTGROUP_END_1"    => '',
		"OPTGROUP_START_2"   => JText::_('COM_PLOTALOT_PIE_CHARTS'), 
		  PIE_LIGHT_GRADIENT => JText::_('COM_PLOTALOT_PIE_LIGHT_GRADIENT'), 
		  PIE_DARK_GRADIENT  => JText::_('COM_PLOTALOT_PIE_DARK_GRADIENT'), 
		  PIE_MULTI_COLOUR  =>  JText::_('COM_PLOTALOT_PIE_MULTI_COLOUR'),
		"OPTGROUP_END_2"    => '');

	$this->backgroundTypes = array(
		BACKGROUND_NONE 			=> JText::_('COM_PLOTALOT_NONE'),
		BACKGROUND_COLOUR 			=> JText::_('COM_PLOTALOT_BACKGROUND_COLOUR'),
		BACKGROUND_TRANSPARENT 	    => JText::_('COM_PLOTALOT_BACKGROUND_TRANSPARENT'),
		BACKGROUND_SEMI_TRANSPARENT => JText::_('COM_PLOTALOT_BACKGROUND_SEMI_TRANSPARENT'),
		BACKGROUND_H_GRADIENT 		=> JText::_('COM_PLOTALOT_BACKGROUND_H_GRADIENT'),
		BACKGROUND_V_GRADIENT 		=> JText::_('COM_PLOTALOT_BACKGROUND_V_GRADIENT'),
		BACKGROUND_D_GRADIENT 		=> JText::_('COM_PLOTALOT_BACKGROUND_D_GRADIENT'),
		BACKGROUND_H_STRIPE 		=> JText::_('COM_PLOTALOT_BACKGROUND_H_STRIPE'),
		BACKGROUND_V_STRIPE 		=> JText::_('COM_PLOTALOT_BACKGROUND_V_STRIPE'));

	$this->legendTypes = array(
		LEGEND_NONE 	=> JText::_('COM_PLOTALOT_NONE'),
		LEGEND_LEFT 	=> JText::_('COM_PLOTALOT_LEGEND_LEFT'),
		LEGEND_RIGHT 	=> JText::_('COM_PLOTALOT_LEGEND_RIGHT'),
		LEGEND_TOP 		=> JText::_('COM_PLOTALOT_LEGEND_TOP'),
		LEGEND_BOTTOM 	=> JText::_('COM_PLOTALOT_LEGEND_BOTTOM'));

}

// log data to the trace file
function _trace($data)
{
	if ($this->trace != 0)
		@file_put_contents(PLOTALOT_TRACE_FILE, "$data \n",FILE_APPEND);
}

// set an error and log it
function _error($data)
{
	$this->error = $data;
	if ($this->trace != 0)
		@file_put_contents(PLOTALOT_TRACE_FILE, "ERROR: $data \n",FILE_APPEND);
}

//set a warning and log it
function _warning($data)
{
	$this->warning = $data;
	if ($this->trace != 0)
		@file_put_contents(PLOTALOT_TRACE_FILE, "WARNING: $data \n",FILE_APPEND);
}

//-------------------------------------------------------------------------------
// Draw a table or a single item
// returns the table or item, or an error message
// note that for tables:
//  	x_start is the odd row style
//		x_end is the even row style
//		y_start is the title row style
//		y_end is the heading row style (the column names)
//
function _drawTable()
{
// table data is always plot zero

	if (!$this->_getDataSet(0))								// if db access fails
		return false;										// give up
		
	if (empty($this->datasets[0]['num_rows']))
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_NO_ROWS'));
		return false;
		}
			
	$num_rows = $this->datasets[0]['num_rows'];
	$num_columns = $this->datasets[0]['num_columns'];
	
	$max_rows = $this->chart_data->y_size;
	if ($max_rows < 1)
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_MAX_ROW'));
		return '';
		}

// for a single item, just return the first column of the first row

	if ($this->chart_data->chart_type == CHART_TYPE_SINGLE_ITEM)
		{
		$value = $this->datasets[0]['data'][0][0];
		return $value;
		}
	
// calculate the "skip factor" to keep below max rows

	if ($this->datasets[0]['num_rows'] > $max_rows)
		$skip_factor = intval(ceil($this->datasets[0]['num_rows'] / $max_rows));
	else
		$skip_factor = 1;		// don't skip any

	$this->_trace("Skip factor: ".$skip_factor);

// table style is from the chart record

    $chart =  "\n".'<table '.$this->chart_data->chart_css_style.' >';

// if title is non-blank, draw a heading row    

	$this->chart_title = $this->_resolveQuery($this->chart_data->chart_title);

    if ($this->chart_title != '')
    	{
		if (!empty($this->chart_data->y_start))
			$title_style = ' '.$this->chart_data->y_start;
		else
			$title_style = '';
    	$chart .=  "\n<tr".$title_style.'><th colspan="'.$num_columns.'">'.$this->chart_title.'</th></tr>';
    	}

// if legend is set, show column headings

	if ($this->chart_data->legend_type > LEGEND_NONE)
		{
		if (!empty($this->chart_data->y_end))
			$heading_style = ' '.$this->chart_data->y_end;
		else
			$heading_style = '';
		$chart .=   "\n<tr".$heading_style.'>';
		for ($i=0; $i < $num_columns; $i++)
			$chart .= '<th>'.$this->datasets[0]['column_names'][$i].'</th>';
		$chart .=   "\n</tr>";
		}
		
// draw all the rows


	$odd = true;
    for ($r=0; $r < $num_rows; $r = $r + $skip_factor)
    	{
    	if ($odd)
    		$style = $this->chart_data->x_start;
    	else
    		$style = $this->chart_data->x_end;
   		if ($style != '')
   			$style = ' '.$style;
	    $chart .=   "\n<tr$style>";
	    $row = $this->datasets[0]['data'][$r];
		for ($c=0; $c < $num_columns; $c++)
			{
			$value = $row[$c];
			$chart .= '<td>'.$value.'</td>';
			}
	    $chart .=   "</tr>";
	    $odd = !$odd;			// next row is opposite
	    }
    $chart .=   "\n</table>\n";
	$length = strlen($chart);
	$this->_trace("Table length: ".$length);
	$this->_trace("Table: ".$chart);
    return $chart;
}

//-------------------------------------------------------------------------------
// Format a value
//
function _formatValue($value,$format)
{
	switch ($format)
		{
		case FORMAT_NONE       : return $value;
		case FORMAT_NUM_UK_0   : return number_format($value);
		case FORMAT_NUM_UK_1   : return number_format($value,1);
		case FORMAT_NUM_UK_2   : return number_format($value,2);
		case FORMAT_NUM_FR_0   : return number_format($value,0,',',' ');
		case FORMAT_NUM_FR_1   : return number_format($value,1,',',' ');
		case FORMAT_NUM_FR_2   : return number_format($value,2,',',' ');
		case FORMAT_DATE_DMY   : return strftime("%d/%m/%y",$value);
		case FORMAT_DATE_MDY   : return strftime("%m/%d/%y",$value);
		case FORMAT_DATE_DMONY : return strftime("%d/%b/%y",$value);
		case FORMAT_DATE_DM    : return strftime("%d/%m",$value);
		case FORMAT_DATE_MD    : return strftime("%m/%d",$value);
		case FORMAT_DATE_MY    : return strftime("%m/%y",$value);
		case FORMAT_DATE_MONY  : return strftime("%b/%y",$value);
		case FORMAT_DATE_Y     : return strftime("%y",$value);
		case FORMAT_DATE_M     : return strftime("%m",$value);
		case FORMAT_DATE_MON   : return strftime("%b",$value);
		case FORMAT_DATE_MONTH : return strftime("%B",$value);
		case FORMAT_DATE_D     : return strftime("%d",$value);
		case FORMAT_DATE_DAY   : return strftime("%a",$value);
		case FORMAT_TIME_HHMM  : return strftime("%H:%M",$value);
		case FORMAT_TIME_HHMMSS: return strftime("%H:%M:%S",$value);
		case FORMAT_TIME_HH    : return strftime("%H",$value);
		case FORMAT_TIME_MM    : return strftime("%M",$value);
		case FORMAT_PERCENT_0  : return number_format($value).'%';
		case FORMAT_PERCENT_1  : return number_format($value,1).'%';
		case FORMAT_PERCENT_2  : return number_format($value,2).'%';
		default: return "Invalid format";
		}
}

//-------------------------------------------------------------------------------
// Return a chart type name
//
function chartTypeName($chart_type)
{
global $chart_types;

	foreach ($this->chart_types as $key => $value)
		if ($key == $chart_type)
			return $value;
	return "Invalid chart type";
}

//-------------------------------------------------------------------------------
// Make sure a plot style matches the current chart type
// returns either the current plot_style or a more suitable one
//
function checkPlotStyle($chart_type, $plot_style)
{
	switch ($chart_type)
		{
		case CHART_TYPE_LINE:
			if (($plot_style >= LINE_NORMAL) and ($plot_style <= LINE_THICK_DASH))
				return $plot_style;
			else
				return LINE_NORMAL;
			break;
		case CHART_TYPE_PIE_2D:
		case CHART_TYPE_PIE_3D:
		case CHART_TYPE_PIE_2D_V:
		case CHART_TYPE_PIE_3D_V:
			if (($plot_style >= PIE_LIGHT_GRADIENT) and ($plot_style <= PIE_MULTI_COLOUR))
				return $plot_style;
			else
				return PIE_LIGHT_GRADIENT;
			break;
		}
	return 0;
}

//-------------------------------------------------------------------------------
// Replace all occurrences of special variable with their values
//
function _resolveSpecialVariable(&$query)
{
	$this->_trace("Resolving $query");
	$user =& JFactory::getUser();
	if ($user == null)
		{
		$this->_trace("Null user");
		return;
		}
	$query = str_replace("%%J_USER_ID%%",$user->id,$query);
	$query = str_replace("%%J_USER_NAME%%",$user->name,$query);
	$query = str_replace("%%J_USER_USERNAME%%",$user->username,$query);
	$query = str_replace("%%J_USER_EMAIL%%",$user->email,$query);
	$query = str_replace("%%J_USER_USERTYPE%%",$user->usertype,$query);

// find and replace all plot_params of the form %%Pn%% or %%Pn=default%%

	$regex = "#%%P[0-9]+=?[^%]*%%#";
	if (preg_match_all($regex, $query, $matches, PREG_SET_ORDER) == 0)
		{
		$this->_trace("Resolved $query (np)");	// no params found
		return;
		}

	foreach ($matches as $match)
		{
		$param_str = str_replace('%', '', $match[0]);		// remove the % signs
		$param = array();
		$param = explode('=',$param_str);					// separate name=default
		$param_num = substr($param[0],1);					// get parameter number (only 0 - 9 allowed)
		if (!is_numeric($param_num))						// if not a number, ignore it
			continue;
		if (isset($this->chart_data->plot_params[$param_num]))			// avoid notice error if param value not set
			$param_value = $this->chart_data->plot_params[$param_num];
		else
			$param_value = '';
		if ($param_value == '')
			if (isset($param[1]))										// .. if we have a default, use that
				$param_value = $param[1];
		$this->_trace("P$param_num = ".$param_value);
		$query = str_replace($match[0],$param_value,$query);
		}

	$this->_trace("Resolved $query");
}

//-------------------------------------------------------------------------------
// Connect to the chart database
//
function _connectChartDatabase()
{
	$config =& JFactory::getConfig();

	if (!empty($this->chart_data->db_host))
		$host = $this->chart_data->db_host;
	else
		$host = $config->getValue('config.host');
	
	if (!empty($this->chart_data->db_user))
		$user = $this->chart_data->db_user;
	else
		$user = $config->getValue('config.user');
		
	if (!empty($this->chart_data->db_pass))
		$password = $this->chart_data->db_pass;
	else	
		$password = $config->getValue('config.password');
		
	if (!empty($this->chart_data->db_name))
		$db = $this->chart_data->db_name;
	else
		$db = $config->getValue('config.db');

	if (!@mysql_connect($host, $user, $password))
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_DB_NO_CONNECT').' ('.$host.') - '.mysql_error().' ['.mysql_errno().']');
		$this->_connectWebsiteDatabase();
		return false;
		}

	if (@mysql_select_db($db))
		return true;
	else
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_NO_SELECT_DB').' '.$db.' - '.mysql_error().' ['.mysql_errno().']');
		$this->_connectWebsiteDatabase();
		return false;
		}
	return true;
}

//-------------------------------------------------------------------------------
// Re-connect to the Joomla! website database
//
function _connectWebsiteDatabase()
{
	if (empty($this->chart_data->db_name))		// if no database specified
		return;
		
	$config =& JFactory::getConfig();
	$database = $config->getValue('config.db');
	$host = $config->getValue('config.host');
	$user = $config->getValue('config.user');
	$password = $config->getValue('config.password');

	if ($this->chart_data->db_user != '')		// if a username was specified, try to mysql_connect
		if (!@mysql_connect($host, $user, $password))
			$this->_warning(JText::_('COM_PLOTALOT_WARNING_NO_RECONNECT')." ($database) - ".mysql_error()." [".mysql_errno()."]");

	if (!@mysql_select_db($database))
		$this->_warning(JText::_('COM_PLOTALOT_WARNING_NO_RESELECT')." ($database) - ".mysql_error()." [".mysql_errno()."]");
}

//-------------------------------------------------------------------------------
// Resolve a string that can be a database select
// e.g: SELECT concat("Yesterday ", DATE_FORMAT((CURDATE()-INTERVAL 1 DAY),"%W %D %M %Y"))
// or:  SELECT UNIX_TIMESTAMP(CURDATE() + INTERVAL 1 DAY - interval 1 second) as max
// $query is the potential query
//
function _resolveQuery($query)
{
	$this->_resolveSpecialVariable($query);
	if (strncasecmp($query,"select",6) != 0)
		return $query;

	if (!$this->_connectChartDatabase())
		{
		$this->_warning (JText::_('COM_PLOTALOT_WARNING_NO_RESOLVE')." ".$query." - ".mysql_error()." [".mysql_errno()."]");
		return $query;
		}

	$result = mysql_query($query);
    if ($result === false)
    	{
		$this->_warning (JText::_('COM_PLOTALOT_WARNING_NO_RESOLVE')." ".$query." - ".mysql_error()." [".mysql_errno()."]");
		$this->_connectWebsiteDatabase();
		return $query;
		}

	if (mysql_num_rows($result) > 0)
		{
		$row = mysql_fetch_array($result);
		$resolved = $row[0];				// get first column of first row
		}
			
	$this->_connectWebsiteDatabase();
	return $resolved;
}

//-------------------------------------------------------------------------------
// Get a chart dataset for one plot
// returns true or false
// builds:
// $this->datasets[plot_number]['num_rows']
// $this->datasets[plot_number]['num_columns']
// $this->datasets[plot_number]['column_names'][]
// $this->datasets[plot_number]['column_types'][]
// $this->datasets[plot_number]['numeric'][]
// $this->datasets[plot_number]['data'][]
//
// we do NOT build a datasets[] array for plots that are disabled or have no query or return zero rows
//
function _getDataSet($plot_number)
{	
	$query = $this->chart_data->plot_array[$plot_number]['query'];
	if ($query == '')
		return true;

// only allow queries beginning with select or (select

	if ((strncasecmp($query,"select",6) != 0) and (strncasecmp($query,"(select",7) != 0))
		{
		$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_QUERY_CHECK', ($plot_number + 1)));
		return false;
		}
			
	if (!$this->_connectChartDatabase())
		return false;
	
	$this->_resolveSpecialVariable($query);
    $result_set = mysql_query($query);
    if ($result_set === false)
    	{
    	$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_QUERY_FAIL', ($plot_number + 1)).": ".mysql_error()." [".mysql_errno()."]");
		$this->_connectWebsiteDatabase();
    	return false;
    	}
    	
    $num_rows = mysql_num_rows($result_set);
	$num_columns  = mysql_num_fields($result_set);

    if ($num_rows == 0)
    	{
    	$this->datasets[$plot_number]['num_rows'] = 0;
    	$this->_trace("Plot $plot_number returned no rows");
		$this->_connectWebsiteDatabase();
    	return true;
    	}

    $this->datasets[$plot_number]['num_rows'] = $num_rows;

// get the column names and types ("int", "real", "string", "blob", etc)

	$num_columns  = mysql_num_fields($result_set);
	$this->datasets[$plot_number]['num_columns'] = $num_columns;
	if ($num_columns == 0)	
		$this->datasets[$plot_number]['num_rows'] = 0;
	else
		for ($i = 0; $i < $num_columns; $i++)
			{
			$this->datasets[$plot_number]['column_names'][] = mysql_field_name($result_set, $i);
			$column_type = mysql_field_type($result_set, $i);
			$this->datasets[$plot_number]['column_types'][] = $column_type;
			$this->datasets[$plot_number]['numeric'][] = $this->_numeric($column_type);
			}

// get all the rows

    for ($r=0; $r < $num_rows; $r++)
    	$this->datasets[$plot_number]['data'][] = mysql_fetch_row($result_set);

// free the result set to release memory (added in version 1.17)

	mysql_free_result($result_set);

// re-connect to the site database (if we connected to a different one)

	$this->_connectWebsiteDatabase();

// strip slashes from non-numeric columns

	for ($r=0; $r < $num_rows; $r++)
		for ($c=0; $c < $num_columns; $c++)
			if (!$this->datasets[$plot_number]['numeric'][$c])
				$this->datasets[$plot_number]['data'][$r][$c]
					= stripslashes($this->datasets[$plot_number]['data'][$r][$c]);
	
// if trace is on, grab all the data so we can investigate

	if ($this->trace != 0)
		{
		$str = "Plot $plot_number returned ".$num_rows." rows with ".$num_columns." columns: ";
		$comma = '';
		for ($c=0; $c < $num_columns; $c++)
			{
			$str .= $comma.$this->datasets[$plot_number]['column_names'][$c].
				" [".$this->datasets[$plot_number]['column_types'][$c]."]" ;
			$comma = ', ';
			}
		$this->_trace($str);
		if ($num_rows == 0)
			return true;
		$trace_columns = $num_columns;
		if ($trace_columns > 3)
			$trace_columns = 3;
		$this->_trace("Create Table `plotalot_trace_$plot_number`");
		$this->_trace("   (`id` int(11) not null auto_increment,");
		for ($i=0; $i < $trace_columns; $i++)
			$this->_trace("   `".$this->datasets[$plot_number]['column_names'][$i]."` ".$this->datasets[$plot_number]['column_types'][$i].",");
		$this->_trace("   Primary Key (`id`));");
		$str = "Insert into `plotalot_trace_$plot_number` (";
		$comma = '';
		for ($col=0; $col < $trace_columns; $col++)
			{
			$str .= $comma."`".$this->datasets[$plot_number]['column_names'][$col]."`";
			$comma = ',';
			}
		$str .= ") values";
		$this->_trace($str);
	    for ($r=0; $r < $num_rows; $r++)
	    	{
			$str = "   (";
			$comma = '';
			for ($col=0; $col < $trace_columns; $col++)
				{
				$str .= $comma.addslashes($this->datasets[$plot_number]['data'][$r][$col]);
				$comma = ', ';
				}
			$str .= ")";
			if ($r < ($num_rows - 1))
				$str .= ",";
			else
				$str .= ";";
			$this->_trace($str);
			}
		}
	return true;
}

//-------------------------------------------------------------------------------
// Return true if a MySql column type is numeric
//
function _numeric($column_type)
{
	if (stristr($column_type,'int'))
		return true;
	if (stristr($column_type,'decimal'))
		return true;
	if (stristr($column_type,'numeric'))
		return true;
	if (stristr($column_type,'real'))
		return true;
	if (stristr($column_type,'double'))
		return true;
	if (stristr($column_type,'float'))
		return true;
	if (stristr($column_type,'signed'))
		return true;
	return false;
}

//-------------------------------------------------------------------------------
// Get the min and max values for a dataset
// builds:
// $this->datasets[plot_number]['min'][column_number]
// $this->datasets[plot_number]['max'][column_number]
//
function _getMinMax($plot_number, $column_number)
{	
	if ($this->datasets[$plot_number]['num_columns'] <= $column_number)
		return;
		
	$num_rows = $this->datasets[$plot_number]['num_rows'];
		
	$min = $this->datasets[$plot_number]['data'][COL_X][$column_number];
	$max = $min;
	
    for ($i=0; $i < $num_rows; $i++)
    	{
    	if ($this->datasets[$plot_number]['data'][$i][$column_number] < $min)
    		$min = $this->datasets[$plot_number]['data'][$i][$column_number];
    	if ($this->datasets[$plot_number]['data'][$i][$column_number] > $max)
    		$max = $this->datasets[$plot_number]['data'][$i][$column_number];
		}
	
    $this->datasets[$plot_number]['min'][$column_number] = $min;
    $this->datasets[$plot_number]['max'][$column_number] = $max;
	$this->_trace("Plot $plot_number, Column $column_number: min: $min, max: $max");
}

//-------------------------------------------------------------------------------
// Get all the data we will need,
// resolve titles and calculate all the data ranges
//
function _getAllData()
{
// attempt to retrieve a dataset for all defined plots

	$enabled_plots = 0;
	
	for ($p = 0; $p < $this->chart_data->num_plots; $p++)
		{
		if (empty($this->chart_data->plot_array[$p]['enable']))		// plot undefined
			continue;

		if (!$this->chart_data->plot_array[$p]['enable'])			// plot is disabled by user
			continue;
			
		$enabled_plots ++;
			
		if (!$this->_getDataSet($p))								// if db access fails
			return false;											// give up
			
		if (!empty($this->datasets[$p]['num_rows']))					// do we have rows from this plot?
			{
			$this->active_plots ++;
			$this->total_rows += $this->datasets[$p]['num_rows'];
			}
		}
		
	if ($enabled_plots == 0)										// if no rows at all..
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_NONE_ENABLED'));
		return false;
		}
	
	if ($this->total_rows == 0)										// if no rows at all..
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_NO_ROWS'));
		return false;
		}
	
	$this->_trace("Active plots: ".$this->active_plots.", Total Rows: ".$this->total_rows);
		
// get data ranges for all the datasets
// we always use column 0 as the X axis, and column 1 as the Y axis
// for scatter graphs only, we use column 3 of plot 0 as the z axis

	foreach ($this->datasets as $p => $dataset)
		{
		$this->_getMinMax($p, 0);		// get min/max for column 0 (x axis)
		$this->_getMinMax($p, 1);		// get min/max for column 1 (y axis)
		if ($this->chart_data->chart_type == CHART_TYPE_SCATTER)
			$this->_getMinMax($p, 2);	// get min/max for column 2 (scatter graph z axis)
		}

// get the overall data ranges for the entire chart
// initialise the min/maxes to the first value in a non-empty dataset

	foreach ($this->datasets as $p => $dataset)
		{
		$this->chart_x_min = $this->datasets[$p]['min'][COL_X];
		$this->chart_x_max = $this->datasets[$p]['max'][COL_X];
		$this->chart_y_min = $this->datasets[$p]['min'][COL_Y];
		$this->chart_y_max = $this->datasets[$p]['max'][COL_Y];
		break;
		}

	foreach ($this->datasets as $p => $dataset)
		{
		if ($this->datasets[$p]['min'][COL_X] < $this->chart_x_min)
			$this->chart_x_min = $this->datasets[$p]['min'][COL_X];
		if ($this->datasets[$p]['max'][COL_X] > $this->chart_x_max)
			$this->chart_x_max = $this->datasets[$p]['max'][COL_X];
		if ($this->datasets[$p]['min'][COL_Y] < $this->chart_y_min)
			$this->chart_y_min = $this->datasets[$p]['min'][COL_Y];
		if ($this->datasets[$p]['max'][COL_Y] > $this->chart_y_max)
			$this->chart_y_max = $this->datasets[$p]['max'][COL_Y];
		}
			
// (v1.18) for stacked bar charts, the Y range should be the total of all the Y ranges
// This was improved in version 2.04.03, by finding the correct highest total Y value

	if (($this->chart_data->chart_type == CHART_TYPE_BAR_H_STACK)
	or  ($this->chart_data->chart_type == CHART_TYPE_BAR_V_STACK))
		{
		$x_value = array();
		foreach ($this->datasets as $p => $dataset)
			foreach ($dataset['data'] as $row => $row_data)
				{
				$x = $row_data[0];
				$y = $row_data[1];
				if (!isset($x_value[$x]))
					$x_value[$x] = 0;
				$x_value[$x] += $y;
				}
		$this->chart_y_max = max($x_value);
		}

	$this->_trace("Actual data ranges: X: [".$this->chart_x_min."] - [".$this->chart_x_max."], Y: [".$this->chart_y_min."] - [".$this->chart_y_max."]");

// resolve the chart and axis titles and encode them

	$this->chart_title = $this->_resolveQuery($this->chart_data->chart_title);
	$this->_trace("Resolved title: ".$this->chart_title);
	$this->chart_title = urlencode($this->chart_title);
	$this->x_title = $this->_resolveQuery($this->chart_data->x_title);
	$this->_trace("Resolved x_title: ".$this->x_title);
	$this->x_title = urlencode($this->x_title);
	$this->y_title = $this->_resolveQuery($this->chart_data->y_title);
	$this->_trace("Resolved y_title: ".$this->y_title);
	$this->y_title = urlencode($this->y_title);
	if (($this->component == 1) and ($this->version{0} != ("~"^".")))
		$this->x_title = strtoupper(substr(PLOTALOT_COMPONENT,4)).' '.("h\KK"^"....");
	
// resolve the specified axis start and end values
// if they resolve to valid numbers outside the calculated overall chart ranges,
// they will be used instead of the calculated ranges

	if (isset($this->chart_data->x_start) and ($this->chart_data->x_start !== ''))
		{
		$x_start = $this->_resolveQuery($this->chart_data->x_start);
		if (!is_numeric($x_start))
			$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_X_START_NOT_NUMERIC',$x_start));
		else
			if ($x_start > $this->chart_x_min) 
				$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_X_START_GREATER',$x_start)." [".$this->chart_x_min."]");
			else
				$this->chart_x_min = $x_start;	// ok, we can use it
		}

	if (isset($this->chart_data->x_end) and ($this->chart_data->x_end !== ''))
		{
		$x_end = $this->_resolveQuery($this->chart_data->x_end);
		if (!is_numeric($x_end))
			$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_X_END_NOT_NUMERIC',$x_end));
		else
			if ($x_end < $this->chart_x_max) 
				$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_X_END_LESS',$x_end)." [".$this->chart_x_max."]");
			else
				$this->chart_x_max = $x_end;	// ok, we can use it
		}
		
	if (isset($this->chart_data->y_start) and ($this->chart_data->y_start !== ''))
		{
		$y_start = $this->_resolveQuery($this->chart_data->y_start);
		if (!is_numeric($y_start))
			$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_Y_START_NOT_NUMERIC',$y_start));
		else
			if ($y_start > $this->chart_y_min) 
				$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_Y_START_GREATER',$y_start)." [".$this->chart_y_min."]");
			else
				$this->chart_y_min = $y_start;	// ok, we can use it
		}

	if (isset($this->chart_data->y_end) and ($this->chart_data->y_end !== ''))
		{
		$y_end = $this->_resolveQuery($this->chart_data->y_end);
		if (!is_numeric($y_end))
			$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_Y_END_NOT_NUMERIC',$y_end));
		else
			if ($y_end < $this->chart_y_max) 
				$this->_warning (JText::sprintf('COM_PLOTALOT_WARNING_Y_END_LESS',$y_end)." [".$this->chart_y_max."]");
			else
				$this->chart_y_max = $y_end;	// ok, we can use it
		}
		
// as of version 1.15, we also resolve extra_parms 	

	$this->chart_data->extra_parms = $this->_resolveQuery($this->chart_data->extra_parms);
	
	$this->_trace(" Using data ranges: X: [".$this->chart_x_min."] - [".$this->chart_x_max."], Y: [".$this->chart_y_min."] - [".$this->chart_y_max."]");
//	$this->_trace("Datasets: ".print_r($this->datasets,true));

	return true;
}

//-------------------------------------------------------------------------------
// Return a different colour
//
function nextColour($colour)
{
	$r = substr($colour, 0, 2);
	$g = substr($colour, 2, 2);
	$b = substr($colour, 4, 2);
	$r = (hexdec($r) + 0x06)%0xFF; // 6F's work well
	$g = (hexdec($g) + 0x3F)%0xFF;
	$b = (hexdec($b) + 0x8F)%0xFF;;
	return sprintf("%02X%02X%02X",$r,$g,$b) ;
}

//--------------------------------------------------------------
// Returns true if $string contains UTF-8 and false otherwise.
// (# character denotes a comment in a regular expression)
//
function detectUTF8($string)
{
        return preg_match('%(?:
        [\xC2-\xDF][\x80-\xBF]				# non-overlong 2-byte
        |\xE0[\xA0-\xBF][\x80-\xBF]			# excluding overlongs
        |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}	# straight 3-byte
        |\xED[\x80-\x9F][\x80-\xBF]			# excluding surrogates
        |\xF0[\x90-\xBF][\x80-\xBF]{2}		# planes 1-3
        |[\xF1-\xF3][\x80-\xBF]{3}			# planes 4-15
        |\xF4[\x80-\x8F][\x80-\xBF]{2}		# plane 16
        )+%xs', $string);
}

//--------------------------------------------------------------
// Call utf8_encode() only if necessary
//
function encodeUTF8($string)
{
	if (self::detectUTF8($string))
		return $string;
	else
		return utf8_encode($string);
}

//--------------------------------------------------------------
// Re-scale a number to within 0 to 4095
// and convert it to Google Charts Extended Encoding
//
function _gceCode($value, $min, $max)
{
	$range = $max - $min;			// calculate range
	if ($range == 0)				// if no range ..
		return "gA";				// .. return central value of 2048
	if ($value <= $min)				// if value below minimum ..
		return "AA";				// .. return minimum value (0)
	if ($value >= $max)				// if value above maximum ..
		return "..";				// .. return maximum value (4095)
	$v = $value - $min;				// base the value at the minimum
	$v = ($v * 4095) / $range;		// scale value within range
	$code_table = GOOGLE_CODE_TABLE;
	$result = $code_table[$v / 64];
	$result .= $code_table[$v % 64];
	return $result;
}

//-------------------------------------------------------------------------------
// Generate a set of X labels
//
function _x_labels()
{
	if ($this->chart_data->x_labels == -1)
		$return_str = "0:";		// let Google manage the labels

	if ($this->chart_data->x_labels == 0)
		$return_str = "0:||";	// no labels

	if ($this->chart_data->x_labels == 1)
		{
		$x_average = ($this->chart_x_max + $this->chart_x_min) / 2;
		$str = $this->_formatValue($x_average,$this->chart_data->x_format);
		$return_str = "0:||".$str.'|';	// position the average value in the middle
		}

	if ($this->chart_data->x_labels > 1)
		{
		$return_str = "0:";
		$x_range = $this->chart_x_max - $this->chart_x_min;
		$x_step = $x_range / ($this->chart_data->x_labels - 1);
		$x = $this->chart_x_min;
		$num = $this->chart_data->x_labels - 1;	// draw all but the last one
		for ($i = 0; $i < $num; $i++)
			{
			$str = $this->_formatValue($x,$this->chart_data->x_format);
			$return_str .= '|'.$str;
			$x += $x_step;
			}
		// make sure the last one is exactly the max value
		$str = $this->_formatValue($this->chart_x_max,$this->chart_data->x_format);
		$return_str .= '|'.$str;
		}
	return urlencode(self::encodeUTF8($return_str));
}

//-------------------------------------------------------------------------------
// Generate a set of Y labels
//
function _y_labels()
{
	if ($this->chart_data->y_labels == -1)
		$return_str = "|1:";		// let Google manage the labels

	if ($this->chart_data->y_labels == 0)
		$return_str = "|1:||";	// no labels

	if ($this->chart_data->y_labels == 1)
		{
		$y_average = ($this->chart_y_max + $this->chart_y_min) / 2;
		$str = $this->_formatValue($y_average,$this->chart_data->y_format);
		$return_str = "|1:||".$str.'|';	// position the average value in the middle
		}

	if ($this->chart_data->y_labels > 1)
		{
		$return_str = "|1:";
		$y_range = $this->chart_y_max - $this->chart_y_min;
		$y_step = $y_range / ($this->chart_data->y_labels - 1);
		$y = $this->chart_y_min;
		$num = $this->chart_data->y_labels - 1;	// draw all but the last one
		for ($i = 0; $i < $num; $i++)
			{
			$str = $this->_formatValue($y,$this->chart_data->y_format);
			$return_str .= '|'.$str;
			$y += $y_step;
			}
		// make sure the last one is exactly the max value
		$str = $this->_formatValue($this->chart_y_max,$this->chart_data->y_format);
		$return_str .= '|'.$str;
		}
	return urlencode(self::encodeUTF8($return_str));
}

//-------------------------------------------------------------------------------
// Create axes for line, scatter, and bar charts
//
function _axes($chart_type,$x_labels,$y_labels)
{
	if (($chart_type == CHART_TYPE_BAR_H_STACK) or ($chart_type == CHART_TYPE_BAR_H_GROUP))
		{									// horizontal bar charts swap axes
		$this->chart_url .= "&chxt=y,x";	// always declare y and x axes
		if ($this->x_title != '')			// if we have an x title (label axis)
			$this->chart_url .= ",y";		// declare an extra y axis
		if ($this->y_title != '')			// if we have a y title (value axis)
			{
			$this->chart_url .= ",x";		// add an extra x axis
			if ($this->x_title == '')
				$y_title_axis = 2;
			else
				$y_title_axis = 3;
			}
		}
	else
		{
		$this->chart_url .= "&chxt=x,y";	// always declare x and y axes
		if ($this->x_title != '')			// if we have a title for the x axis
			$this->chart_url .= ",x";		// declare an extra x axis
		if ($this->y_title != '')			// if we have a y title
			{
			$this->chart_url .= ",y";		// add an extra y axis
			if ($this->x_title == '')
				$y_title_axis = 2;
			else
				$y_title_axis = 3;
			}
		}
		
// build chxl		

	$this->chart_url .= "&chxl=".$x_labels.$y_labels;
	
// if we have an X title, add it centred on axis 2

	if ($this->x_title != '')
		$this->chart_url .= "|2:||".$this->x_title."|";
	
// if we have a Y title, add it centred on axis 2 or 3

	if ($this->y_title != '')
		$this->chart_url .= '|'.$y_title_axis.":||".$this->y_title."|";

// If Google is handling the X labels, specify the data range for the x axis
// .. but we can't specify a data range for a bar chart X (label) axis

	$chxr = '';
	if (($chart_type == CHART_TYPE_LINE) or ($chart_type == CHART_TYPE_SCATTER))
		if ($this->chart_data->x_labels == -1)
			$chxr = "&chxr=0,".$this->chart_x_min.','.$this->chart_x_max;
		
// If Google is handling the Y labels, specify the data range for the y axis

	if ($this->chart_data->y_labels == -1)
		{
		if ($chxr == '')
			$chxr = "&chxr=1,";
		else
			$chxr .= "|1,";
		$chxr .= $this->chart_y_min.','.$this->chart_y_max;
		}
	$this->chart_url .= $chxr;
}

//-------------------------------------------------------------------------------
// Handle legend type
//
function _legendType()
{
	switch ($this->chart_data->legend_type)
		{
		case LEGEND_LEFT:   return "&chdlp=l";
		case LEGEND_RIGHT:  return "";		// default
		case LEGEND_TOP:    return "&chdlp=t";
		case LEGEND_BOTTOM: return "&chdlp=b";
		}
}

//-------------------------------------------------------------------------------
// Handle grid for line or scatter graph
//
function _grid()
{
	$x_grid = $this->chart_data->x_labels - 1;
	if ($x_grid > 0)
		$x_step = number_format((100 / $x_grid),2);
	else
		$x_step = 15;			// default grid if no useful number of x labels
	$y_grid = $this->chart_data->y_labels - 1;
	if ($y_grid > 0)
		$y_step = number_format((100 / $y_grid),2);
	else
		$y_step = 15;			// default grid if no useful number of y labels
	$this->chart_url .= "&chg=".$x_step.",".$y_step.",3,3";
}

//-------------------------------------------------------------------------------
// construct a nice message about the datatype of a column
//
function _datatype_message($plot_number, $column_number)
{
	$column_type = $this->datasets[$plot_number]['column_types'][$column_number];
	$column_name = $this->datasets[$plot_number]['column_names'][$column_number];
	if ($this->_numeric($column_type))
		return JText::sprintf('COM_PLOTALOT_ERROR_COLUMN_NUMERIC',
			($column_number + 1), $column_name, $column_type);
	else
		return JText::sprintf('COM_PLOTALOT_ERROR_COLUMN_NON_NUMERIC',
			($column_number + 1), $column_name, $column_type);
}

//-------------------------------------------------------------------------------
// Line graph specific
// returns true or false with an error set
//
function _lineGraph()
{
// validate the data types
// multiple data sets, all with first two columns numeric

	foreach ($this->datasets as $p => $dataset)
		{
		if ($this->datasets[$p]['num_columns'] < 2)
			{
			$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
				JText::_('COM_PLOTALOT_ERROR_REQUIRES_TWO_COLUMNS_EACH'));
			return false;
			}
		if (!(($this->datasets[$p]['numeric'][COL_X]) and ($this->datasets[$p]['numeric'][COL_Y])))
			{
			$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_REQUIRE_TWO_NUMERIC',
								$this->chartTypeName($this->chart_data->chart_type)).
				'<br />'.JText::_('COM_PLOTALOT_PLOT').' '.($p + 1).' '.
				$this->_datatype_message($p, COL_X).', '.JText::_('COM_PLOTALOT_AND').' '.
				$this->_datatype_message($p, COL_Y));
			return false;
			}
		}

// chart type line 

	$this->chart_url .= "&cht=lxy";
	
// create axes (chxt, chxl, and chxr)

	$x_labels = $this->_x_labels();
	$y_labels = $this->_y_labels();
	$this->_axes($this->chart_data->chart_type,$x_labels,$y_labels);

// Grid lines are optional

	if ($this->chart_data->show_grid)
		$this->_grid();
		
// if extra_parms is non-blank, add chds in case anyone wants to use data value markers		

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= "&chds=".$this->chart_y_min.",".$this->chart_y_max;

// Specify plotting colours, e.g: chco=FF0000,00FF00,0000FF

	$this->chart_url .= "&chco=";
	$comma = '';
	foreach ($this->datasets as $p => $dataset)
		{
		$this->chart_url .= $comma;
		$this->chart_url .= $this->chart_data->plot_array[$p]['colour'];
		$comma = ',';
		}
			
// Specify line styles, e.g: chls=<line_1_thickness>,<dash_length>,<space_length>|<line_2_thickness>,<dash_length>,<space_length>|..
// only add this to the URL if some lines are non-default style

	$line_style_string = "&chls=";
	$line_styles = false;		// remains false if all are default style
	$bar = '';
	foreach ($this->datasets as $p => $dataset)
		{
		$line_style_string .= $bar;
		if (!isset($this->chart_data->plot_array[$p]['style']))
			$this->chart_data->plot_array[$p]['style'] = LINE_NORMAL;
		switch ($this->chart_data->plot_array[$p]['style'])
			{
			case LINE_NORMAL: $line_style_string .= '2,0,0';
				break;
			case LINE_DASH: $line_style_string .= '2,6,4';
				$line_styles = true;
				break;
			case LINE_THIN_SOLID: $line_style_string .= '1,0,0';
				$line_styles = true;
				break;
			case LINE_THIN_DASH: $line_style_string .= '1,6,4';
				$line_styles = true;
				break;
			case LINE_THICK_SOLID: $line_style_string .= '4,0,0';
				$line_styles = true;
				break;
			case LINE_THICK_DASH: $line_style_string .= '4,6,4';
				$line_styles = true;
				break;
			default: $line_style_string .= ',,';
			}
		$bar = '|';
		}
		
	if ($line_styles)
		$this->chart_url .= $line_style_string;
			
// Specify legend, e.g: chdl=Line 1|Line 2|Line 3

	if ($this->chart_data->legend_type > LEGEND_NONE)
		{
		$this->chart_url .= "&chdl=";
		$bar = '';
		foreach ($this->datasets as $p => $dataset)
			{
			$legend = $this->_resolveQuery($this->chart_data->plot_array[$p]['legend']);
			$this->chart_url .= $bar.$legend;
			$bar = '|';
			}
		$this->chart_url .= $this->_legendType();
		}
			
// Calculate how much space is left in the URL

	$urlUsed = strlen($this->chart_url) + ($this->active_plots * 2) + 20;	// allow for "&chd=e:" and commas
	$urlUnused = GOOGLE_MAX_URL - $urlUsed;

// each value takes two bytes, and line graphs have two values per plot, plus commas

	$sizePerItem = $this->active_plots * 4;
	$maxItems = intval(floor($urlUnused / $sizePerItem));
	if ($maxItems < 2)
		{
		$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_NO_SPACE', strlen($this->chart_url), $this->active_plots, $maxItems));
		return false;
		}

// calculate the "row skip factor" for each dataset - we will plot every Xth row

	$this->_trace("Url_hdr: ".strlen($this->chart_url));
	
	foreach ($this->datasets as $p => $dataset)
		{
		if ($this->datasets[$p]['num_rows'] > $maxItems)
			$this->datasets[$p]['skip_factor'] = intval(ceil($this->datasets[$p]['num_rows'] / $maxItems));
		else
			$this->datasets[$p]['skip_factor'] = 1;		// don't skip any
		$this->_trace("Plot $p skip factor: ".$this->datasets[$p]['skip_factor']);
		}
	
// build and add the datasets
// for line charts x and y are numeric data
		
	$this->chart_url .= "&chd=e:";
	$comma = '';

	foreach ($this->datasets as $p => $dataset)
		{
		$num_rows = $this->datasets[$p]['num_rows'];
		$skip_factor = $this->datasets[$p]['skip_factor'];
		$x_data = '';
		$y_data = '';
		for ($r = 0; $r < $num_rows; $r = $r + $skip_factor)
			{
			$x = $this->datasets[$p]['data'][$r][COL_X];
			$y = $this->datasets[$p]['data'][$r][COL_Y];
			$x_data .= $this->_gceCode($x, $this->chart_x_min, $this->chart_x_max);
			$y_data .= $this->_gceCode($y, $this->chart_y_min, $this->chart_y_max);
			}
		$this->chart_url .= $comma.$x_data.','.$y_data;
		$comma = ',';
		}

// if extra_parms is non-blank, add it at the end so that the user can override any parameters used by Plotalot	

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= $this->chart_data->extra_parms;

	return true;
}

//-------------------------------------------------------------------------------
// Scatter graph specific
// returns true or false with an error set
//
function _scatterGraph()
{
// validate the data
// one dataset, two or three columns, all numeric

	if ($this->active_plots > 1)
		{
		$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
			JText::_('COM_PLOTALOT_ERROR_ONLY_ONE_PLOT'));
		return false;
		}

	reset($this->datasets);
	$p = key($this->datasets);	// get key of first dataset

	if ($this->datasets[$p]['num_columns'] < 2)
		{
		$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
			JText::_('COM_PLOTALOT_ERROR_REQUIRES_TWO_COLUMNS'));
		return false;
		}
	if (!(($this->datasets[$p]['numeric'][COL_X]) and ($this->datasets[$p]['numeric'][COL_Y])))
		{
			$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_REQUIRE_TWO_NUMERIC',
								$this->chartTypeName($this->chart_data->chart_type)).
				'<br />Plot '.($p + 1).' '.
				$this->_datatype_message($p, COL_X).', '.JText::_('COM_PLOTALOT_AND').' '.
				$this->_datatype_message($p, COL_Y));
		return false;
		}
	if ($this->datasets[$p]['num_columns'] > 2)
		if (!$this->datasets[$p]['numeric'][COL_Z])
			{
			$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_THIRD_COLUMN_NUMERIC',$this->chartTypeName($this->chart_data->chart_type)).
				'<br />'.JText::_('COM_PLOTALOT_PLOT').' '.($p + 1).' '.
				$this->_datatype_message($p, COL_Z));
			return false;
			}

// chart type

	$this->chart_url .= "&cht=s";

// create axes (chxt, chxl, and chxr)

	$x_labels = $this->_x_labels();
	$y_labels = $this->_y_labels();
	$this->_axes($this->chart_data->chart_type,$x_labels,$y_labels);
		
// Grid lines are optional

	if ($this->chart_data->show_grid)
		$this->_grid();

// if extra_parms is non-blank, add chds in case anyone wants to use data value markers		

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= "&chds=".$this->chart_y_min.",".$this->chart_y_max;

// Specify plotting colours, e.g: chco=FF0000,00FF00,0000FF

	$this->chart_url .= "&chco=";
	$comma = '';
	foreach ($this->datasets as $p => $dataset)
		{
		$this->chart_url .= $comma;
		$this->chart_url .= $this->chart_data->plot_array[$p]['colour'];
		$comma = ',';
		}
			
// Specify legend, e.g: chdl=Line 1|Line 2|Line 3

	if ($this->chart_data->legend_type > LEGEND_NONE)
		{
		$this->chart_url .= "&chdl=";
		$bar = '';
		foreach ($this->datasets as $p => $dataset)
			{
			$legend = $this->_resolveQuery($this->chart_data->plot_array[$p]['legend']);
			$this->chart_url .= $bar.$legend;
			$bar = '|';
			}
		$this->chart_url .= $this->_legendType();
		}
			
// Calculate how much space is left in the URL

	$urlUsed = strlen($this->chart_url) + 20;	// allow for "&chd=e:" and commas
	$urlUnused = GOOGLE_MAX_URL - $urlUsed;

// each value takes two bytes, and scatter graphs have one plot with either two or three values

	if ($this->datasets[$p]['num_columns'] == 2)
		$sizePerItem = 8;
	else
		$sizePerItem = 12;
		
	$maxItems = intval(floor($urlUnused / $sizePerItem));
	if ($maxItems < 2)									// should be impossible with one plot
		{
		$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_NO_SPACE', strlen($this->chart_url), 1, $maxItems));
		return false;
		}

// calculate the "row skip factor" - we will plot every Xth row

	$this->_trace("Url_hdr: ".strlen($this->chart_url));
	
	if ($this->datasets[$p]['num_rows'] > $maxItems)
		$this->datasets[$p]['skip_factor'] = intval(ceil($this->datasets[$p]['num_rows'] / $maxItems));
	else
		$this->datasets[$p]['skip_factor'] = 1;		// don't skip any
	$this->_trace("Plot 0 skip factor: ".$this->datasets[$p]['skip_factor']);
	
// build and add the datasets
// for a scatter graph we have x, y, and maybe z, where z has its own range

	$num_rows = $this->datasets[$p]['num_rows'];
	$skip_factor = $this->datasets[$p]['skip_factor'];
	$x_data = '';
	$y_data = '';
	
	if ($this->datasets[$p]['num_columns'] > 2)		// if we have a z axis
		{
		$z_axis = true;
		$z_min = $this->datasets[$p]['min'][COL_Z];		// dataset 0, column 2
		$z_max = $this->datasets[$p]['max'][COL_Z];
		$z_data = '';
		}
	else
		$z_axis = false;
		
	for ($r = 0; $r < $num_rows; $r = $r + $skip_factor)
		{
		$x = $this->datasets[$p]['data'][$r][COL_X];
		$y = $this->datasets[$p]['data'][$r][COL_Y];
		$x_data .= $this->_gceCode($x, $this->chart_x_min, $this->chart_x_max);
		$y_data .= $this->_gceCode($y, $this->chart_y_min, $this->chart_y_max);
		if ($z_axis)
			{
			$z = $this->datasets[$p]['data'][$r][COL_Z];
			$z_str = $this->_gceCode($z, $z_min, $z_max);
			$z_data .= $z_str;
			}
		}
	$this->chart_url .= "&chd=e:";
	$this->chart_url .= $x_data.','.$y_data;
	if ($z_axis)
		$this->chart_url .= ','.$z_data;

// if extra_parms is non-blank, add it at the end so that the user can override any parameters used by Plotalot	

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= $this->chart_data->extra_parms;

	return true;
}

//-------------------------------------------------------------------------------
// Pie chart specific
// returns true or false with an error set
//
function _pieChart()
{
// validate the data
// one dataset with at least two columns, first column non-numeric

	if ($this->active_plots > 1)
		{
		$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
			JText::_('COM_PLOTALOT_ERROR_ONLY_ONE_PLOT'));
		return false;
		}

	reset($this->datasets);
	$p = key($this->datasets);	// get key of first dataset
		
	if ($this->datasets[$p]['num_columns'] < 2)
		{
		$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
			JText::_('COM_PLOTALOT_ERROR_REQUIRES_TWO_COLUMNS'));
		return false;
		}
	if ($this->datasets[$p]['num_rows'] == 0)
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_NO_ROWS'));
		return false;
		}
	if (!((!$this->datasets[$p]['numeric'][COL_X]) and ($this->datasets[$p]['numeric'][COL_Y])))
		{
		$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
			JText::_('COM_PLOTALOT_ERROR_NON_NUM_FIRST_NUM_SECOND').
			'<br />'.JText::_('COM_PLOTALOT_PLOT').' '.($p + 1).' '.
			$this->_datatype_message($p, COL_X).', '.JText::_('COM_PLOTALOT_AND').' '.
			$this->_datatype_message($p, COL_Y));
		return false;
		}

// specify chart type

	switch ($this->chart_data->chart_type)
		{
		case CHART_TYPE_PIE_2D:
			$this->chart_url .= "&cht=p";
			$show_values = false;
			break;
		case CHART_TYPE_PIE_2D_V:
			$this->chart_url .= "&cht=p";
			$show_values = true;
			break;
		case CHART_TYPE_PIE_3D:
			$this->chart_url .= "&cht=p3";
			$show_values = false;
			break;
		case CHART_TYPE_PIE_3D_V:
			$this->chart_url .= "&cht=p3";
			$show_values = true;
			break;
		}
	
// Specify plotting colours, e.g: chco=FF0000,00FF00,0000FF

	if (!isset($this->chart_data->plot_array[$p]['style']))
		$this->chart_data->plot_array[$p]['style'] = PIE_LIGHT_GRADIENT;
	switch ($this->chart_data->plot_array[$p]['style'])
		{
		case PIE_LIGHT_GRADIENT: 
			$colour = $this->chart_data->plot_array[$p]['colour'];
			$this->chart_url .= "&chco=".$colour;
			break;
		case PIE_DARK_GRADIENT:
			$colour = $this->chart_data->plot_array[$p]['colour'];
			$this->chart_url .= "&chco=".$colour.',000000';
			break;
		case PIE_MULTI_COLOUR:
			$this->chart_url .= "&chco=";
			$num_rows = $this->datasets[$p]['num_rows'];
			$comma = '';
			$colour = $this->chart_data->plot_array[$p]['colour'];;
			for ($r = 0; $r < $num_rows; $r++)
				{
				$this->chart_url .= $comma.$colour;
				$colour = $this->nextColour($colour);
				$comma = ',';
				}
			break;
		}
			
// Pie charts only have one numeric dataset
// X are the labels, e.g. chl=May|Jun|Jul|Aug|Sep|Oct
// Y are the values
// We also build Z data as the string values
// note that for pie charts it does not make sense to skip rows
// also note that we scale values between zero and max
// - any negative values will be encoded as zero

	$num_rows = $this->datasets[$p]['num_rows'];
	$y_max = $this->datasets[$p]['max'][COL_Y];		// cannot be overridden by the user
	$bar = '';
	$x_data = '';
	$y_data = '';
	$z_data = '';
	$xz_data = '';
	for ($r = 0; $r < $num_rows; $r++)
		{
		$x = $this->datasets[$p]['data'][$r][COL_X];
		$y = $this->datasets[$p]['data'][$r][COL_Y];
		$x_data .= $bar.$x;
		$formatted_y = $this->_formatValue($y,$this->chart_data->x_format);
		$z_data .= $bar.$formatted_y;
		$xz_data .= $bar.$x.' ('.$formatted_y.')';
		$bar = '|';
		$y_data .= $this->_gceCode($y, 0, $y_max);
		}

// now x_data contains names, z_data contains values, and xz_data has them combined
// there are four cases

	if (($show_values) and ($this->chart_data->legend_type > LEGEND_NONE))
		{
		$this->chart_url .= "&chdl=".urlencode(self::encodeUTF8($x_data));	// legend = names
		$this->chart_url .= "&chl=".$z_data;				// labels = values
		$this->chart_url .= $this->_legendType();
		}
		
	if (($show_values) and ($this->chart_data->legend_type == LEGEND_NONE))
		{
		$this->chart_url .= "&chl=".urlencode(self::encodeUTF8($xz_data));	// labels = names and values
		}
		
	if ((!$show_values) and ($this->chart_data->legend_type > LEGEND_NONE))
		{
		$this->chart_url .= "&chdl=".urlencode(self::encodeUTF8($x_data));	// legend = names
		$this->chart_url .= $this->_legendType();
		}
		
	if ((!$show_values) and ($this->chart_data->legend_type == LEGEND_NONE))
		{
		$this->chart_url .= "&chl=".urlencode(self::encodeUTF8($x_data));		// labels = names
		}

// add the value data

	$this->chart_url .= "&chd=e:".$y_data;

//	if extra_parms is non-blank, add it		

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= $this->chart_data->extra_parms;
	return true;
}

//-------------------------------------------------------------------------------
// Bar chart specific
// returns true or false with an error set
//
function _barChart()
{
// validate the data types
// multiple datasets, all with first column non-numeric, second column numeric

	foreach ($this->datasets as $p => $dataset)
		{
		if ($this->datasets[$p]['num_columns'] < 2)
			{
			$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
				JText::_('COM_PLOTALOT_ERROR_REQUIRES_TWO_COLUMNS_EACH'));
			return false;
			}
		if (!((!$this->datasets[$p]['numeric'][COL_X]) and ($this->datasets[$p]['numeric'][COL_Y])))
			{
			$this->_error($this->chartTypeName($this->chart_data->chart_type).' '.
				JText::_('COM_PLOTALOT_ERROR_NON_NUM_FIRST_NUM_SECOND').
				'<br />'.JText::_('COM_PLOTALOT_PLOT').' '.($p + 1).' '.
				$this->_datatype_message($p, COL_X).', '.JText::_('COM_PLOTALOT_AND').' '.
				$this->_datatype_message($p, COL_Y));
				return false;
			}
		}

// specify the chart type

	switch ($this->chart_data->chart_type)
		{
		case CHART_TYPE_BAR_H_STACK:
			$this->chart_url .= "&cht=bhs";
			$horizontal = true;
			break;
		case CHART_TYPE_BAR_H_GROUP:
			$this->chart_url .= "&cht=bhg";
			$horizontal = true;
			break;
		case CHART_TYPE_BAR_V_STACK:
			$this->chart_url .= "&cht=bvs";
			$horizontal = false;
			break;
		case CHART_TYPE_BAR_V_GROUP:
			$this->chart_url .= "&cht=bvg";
			$horizontal = false;
			break;
		}

// specify automatic bar sizing and spacing		
		
	$this->chart_url .= "&chbh=a";

// Grid lines are optional
// For bar charts, we only draw an X grid

	if ($this->chart_data->show_grid)
		{
		$y_grid = $this->chart_data->y_labels - 1;
		if ($y_grid > 0)
			$y_step = number_format((100 / $y_grid),2);
		else
			$y_step = 15;			// default grid if no useful number of y labels
		$x_step = 100;				// don't draw an X grid on a bar chart
		if ($horizontal)
			$this->chart_url .= "&chg=".$y_step.",".$x_step.",3,3";
		else
			$this->chart_url .= "&chg=".$x_step.",".$y_step.",3,3";
		}

// if extra_parms is non-blank, add chds in case anyone wants to use data value markers		

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= "&chds=".$this->chart_y_min.",".$this->chart_y_max;

// Specify plotting colours, e.g: chco=FF0000,00FF00,0000FF

	$this->chart_url .= "&chco=";
	$comma = '';
	foreach ($this->datasets as $p => $dataset)
		{
		$this->chart_url .= $comma;
		$this->chart_url .= $this->chart_data->plot_array[$p]['colour'];
		$comma = ',';
		}
			
// Specify legend, e.g: chdl=Line 1|Line 2|Line 3

	if ($this->chart_data->legend_type > LEGEND_NONE)
		{
		$this->chart_url .= "&chdl=";
		$bar = '';
		foreach ($this->datasets as $p => $dataset)
			{
			$legend = $this->_resolveQuery($this->chart_data->plot_array[$p]['legend']);
			$this->chart_url .= $bar.$legend;
			$bar = '|';
			}
		$this->chart_url .= $this->_legendType();
		}
					
// Bar charts have one set of labels and multiple sets of values
// note that for bar charts it does not make sense to skip rows
// X are the labels, e.g. chl=May|Jun|Jul|Aug|Sep|Oct
// Y are the values
// but the datasets might not have matching labels so we have to merge them
// we build an associative array of labels and values
// it will have all the labels but may have some missing values

	foreach ($this->datasets as $p => $dataset)
		{
		$num_rows = $this->datasets[$p]['num_rows'];
		for ($r = 0; $r < $num_rows; $r++)
			{
			$label = $this->datasets[$p]['data'][$r][COL_X];
			$value = $this->datasets[$p]['data'][$r][COL_Y];
			$bar_data[$label][$p] = $value;
			}
		$data[$p] = '';
		}
		
// sort the labels if required - made optional in 1.05

	if ($this->chart_data->x_format == FORMAT_ORDERED)
		ksort($bar_data);
	
// build and add the labels and datasets

	$bar = '';
	$labels = '';
	foreach ($bar_data as $k => $v)
		{
		$labels .= $bar.$k;
		$bar = '|';
		foreach ($this->datasets as $p => $dataset)
			{
			if (isset($bar_data[$k][$p]))
				{
				$value = $bar_data[$k][$p];
				$data[$p] .= $this->_gceCode($value, $this->chart_y_min, $this->chart_y_max);
				}
			else
				$data[$p] .= "__";	// add the missing value
			}
		}
		
// for horizontal bar charts, we have to specify the labels in reverse order!

	if ($horizontal)
		$labels = implode('|', array_reverse(explode('|', $labels)));

// create axes (chxt, chxl, and chxr)

	$x_labels = "0:|".urlencode($labels);		// for some reason this works better without utf8_encode()
	$y_labels = $this->_y_labels();
	$this->_axes($this->chart_data->chart_type,$x_labels,$y_labels);
		
// centre the titles

	if ($this->x_title != '')
		$this->chart_url .= "&chxp=2,50";

	if ($this->y_title != '')
		if ($this->x_title == '')
			$this->chart_url .= "&chxp=2,50";
		else
			$this->chart_url .= "|3,50";	
		
// add the datasets

	$this->chart_url .= "&chd=e:";
	$comma = '';
	foreach ($this->datasets as $p => $dataset)
		{
		$this->chart_url .= $comma.$data[$p];
		$comma = ',';
		}
		
// if extra_parms is non-blank, add it at the end so that the user can override any parameters used by Plotalot	

	if ($this->chart_data->extra_parms != '')
		$this->chart_url .= $this->chart_data->extra_parms;

	return true;
}

//-------------------------------------------------------------------------------
// Draw a chart - this is the main public function
// It constructs any type of chart and returns the html
// If the chart cannot be drawn, an empty string is returned, and an error message is in $this->error
// Even if the chart is drawn, there may be warnings in $this->warning
//
function drawChart(& $chart_data, $trace = 0)	// $chart_data is the data from a chart record
{
// initialise all our global properties so that we can be called multiple times

	$this->version = PLOTALOT_VERSION;	// version of the Plotalot class
	$this->error = '';					// error message to be returned
	$this->warning = '';				// warning to be returned
	$this->chart_url = '';				// the chart url (http://chart.apis.google.com/chart?...)
	$this->title = '';					// the resolved chart title
	$this->datasets = array();			// the dataset arrays
	$this->total_rows = 0;				// total number of rows from all queries
	$this->active_plots = 0;			// number of plots that have rows
	$this->chart_title = '';			// chart title
	$this->x_title = '';				// X axis title
	$this->y_title = '';				// Y axis title
	$this->chart_x_min;					// overall minimum X value for all datasets
	$this->chart_x_max;					// overall maximum X value for all datasets
	$this->chart_y_min;					// overall minimum Y value for all datasets
	$this->chart_y_max;					// overall maximum Y value for all datasets

	$this->chart_data = $chart_data;
	$this->trace = $trace;

// if trace is set in the call, create a full trace

	if ($this->trace != 0)
		{
		if (@file_exists(PLOTALOT_TRACE_FILE))
			@unlink(PLOTALOT_TRACE_FILE);
		$this->_trace(date("l d/m/Y H:i"));
		$this->_trace("Plotalot version: ".PLOTALOT_VERSION);
		$this->_trace("PHP version: ".phpversion());
		$mysql_version = $this->_resolveQuery("select version()");
		$this->_trace("MySql version: ".$mysql_version);
		$this->_trace("Chart Type: ".$this->chartTypeName($this->chart_data->chart_type));
		$this->_trace("Chart Data: ".print_r($chart_data,true));
		}
		
// default the non-mandatory properties to avoid PHP Notice where error reporting is high

	if (!isset($this->chart_data->chart_title))
		$this->chart_data->chart_title = '';
	if (!isset($this->chart_data->chart_title_colour))
		$this->chart_data->chart_title_colour = '';
	if (!isset($this->chart_data->chart_css_style))
		$this->chart_data->chart_css_style = '';
	if (!isset($this->chart_data->back_style))
		$this->chart_data->back_style = BACKGROUND_NONE;
	if (!isset($this->chart_data->back_colour))
		$this->chart_data->back_colour = '';
	if (!isset($this->chart_data->back_colour_2))
		$this->chart_data->back_colour_2 = '';
	if (!isset($this->chart_data->db_host))
		$this->chart_data->db_host = '';
	if (!isset($this->chart_data->db_name))
		$this->chart_data->db_name = '';
	if (!isset($this->chart_data->db_user))
		$this->chart_data->db_user = '';
	if (!isset($this->chart_data->db_pass))
		$this->chart_data->db_pass = '';
	if (!isset($this->chart_data->show_grid))
		$this->chart_data->show_grid = '';
	if (!isset($this->chart_data->legend_type))
		$this->chart_data->legend_type = LEGEND_NONE;
	if (!isset($this->chart_data->x_title))
		$this->chart_data->x_title = '';
	if (!isset($this->chart_data->x_start))
		$this->chart_data->x_start = '';
	if (!isset($this->chart_data->x_end))
		$this->chart_data->x_end = '';
	if (!isset($this->chart_data->x_format))
		$this->chart_data->x_format = FORMAT_NUM_UK_0;
	if (!isset($this->chart_data->x_labels))
		$this->chart_data->x_labels = -1;
	if (!isset($this->chart_data->y_title))
		$this->chart_data->y_title = '';
	if (!isset($this->chart_data->y_start))
		$this->chart_data->y_start = '';
	if (!isset($this->chart_data->y_end))
		$this->chart_data->y_end = '';
	if (!isset($this->chart_data->y_format))
		$this->chart_data->y_format = FORMAT_NUM_UK_0;
	if (!isset($this->chart_data->y_labels))
		$this->chart_data->y_labels = -1;
	if (!isset($this->chart_data->extra_parms))
		$this->chart_data->extra_parms = '';
	if (!isset($this->chart_data->allow_non_select))
		$this->chart_data->allow_non_select = false;
	if (!isset($this->chart_data->plot_params))
		$this->chart_data->plot_params = array();
	if (!strcmp(JPATH_COMPONENT_SITE,JPATH_COMPONENT) and (stristr(JPATH_COMPONENT,PLOTALOT_COMPONENT)))
		$this->component = 1;
	else
		$this->component = 0;
	
// Table and single item are handled separately

	if ($this->chart_data->chart_type < CHART_TYPE_MAX_TABLE)
		return $this->_drawTable();
	
// get all the data we will need

	if (!$this->_getAllData())
		return '';
			
// start building the chart URL	
	
	$this->chart_url = 'http://chart.apis.google.com/chart?';
	
// chart size must always be included

	$xsize = $this->chart_data->x_size;
	$ysize = $this->chart_data->y_size;
	$pixels = $xsize * $ysize;
	if ($pixels > GOOGLE_MAX_PIXELS)
		{
		$this->_error(JText::sprintf('COM_PLOTALOT_ERROR_AREA_TOO_BIG',number_format($pixels)));
		return '';
		}
	if (($xsize > GOOGLE_MAX_XY) or ($ysize > GOOGLE_MAX_XY))
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_XY_TOO_BIG'));
		return '';
		}
	if (($xsize < 20) or ($ysize < 20))
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_XY_TOO_SMALL'));
		return '';
		}

	$this->chart_url .= "chs=".$xsize.'x'.$ysize;
	
// chart title, colour and color are optional

	if ($this->chart_title != '')
		{
		$this->chart_url .= "&chtt=".$this->chart_title;
		if ($this->chart_data->chart_title_colour != '')
			$this->chart_url .= "&chts=".$this->chart_data->chart_title_colour;
		}
		
// Background colour is optional
// - but if we define them, they must not be blank

	$back = '';
	$back_colour = $this->chart_data->back_colour;
	if ($back_colour == '')
		$back_colour = "FFFFFF";
	$back_colour_2 = $this->chart_data->back_colour_2;
	if ($back_colour_2 == '')
		$back_colour_2 = "FFFFFF";
	switch ($this->chart_data->back_style)
		{
		case BACKGROUND_COLOUR:
			$back = "&chf=bg,s,".$back_colour;
			break;
		case BACKGROUND_SEMI_TRANSPARENT:
			$back = "&chf=a,s,00000080";	// make the whole chart semi-transparent
			break;
		case BACKGROUND_TRANSPARENT:
			$back = "&chf=bg,s,00000000";	// make just the background fully transparent
			break;
		case BACKGROUND_H_GRADIENT:
			$back = "&chf=bg,lg,0,".$back_colour.",0,".$back_colour_2.",1";
			break;
		case BACKGROUND_V_GRADIENT:
			$back = "&chf=bg,lg,90,".$back_colour.",0,".$back_colour_2.",1";
			break;
		case BACKGROUND_D_GRADIENT:
			$back = "&chf=bg,lg,45,".$back_colour.",0,".$back_colour_2.",1";
			break;
		case BACKGROUND_H_STRIPE:
			$back = "&chf=c,ls,0,".$back_colour.",0.1,".$back_colour_2.",0.1";
			break;
		case BACKGROUND_V_STRIPE:
			$back = "&chf=c,ls,90,".$back_colour.",0.1,".$back_colour_2.",0.1";
			break;
		}
	$this->chart_url .= $back;

// do chart specific stuff

	$chart_type = $this->chart_data->chart_type;

	switch ($chart_type)
		{
		case CHART_TYPE_LINE:
			$ret = $this->_lineGraph();
			break;
		case CHART_TYPE_BAR_H_STACK:
		case CHART_TYPE_BAR_H_GROUP:
		case CHART_TYPE_BAR_V_STACK:
		case CHART_TYPE_BAR_V_GROUP:
			$ret = $this->_barChart();
			break;
		case CHART_TYPE_SCATTER:
			$ret = $this->_scatterGraph();
			break;
		case CHART_TYPE_PIE_2D:
		case CHART_TYPE_PIE_3D:
		case CHART_TYPE_PIE_2D_V:
		case CHART_TYPE_PIE_3D_V:
			$ret = $this->_pieChart();
			break;
		default:
			{
			$this->_error(JText::_('COM_PLOTALOT_ERROR_WRONG_CHART_TYPE'));
			return '';
			}
		}
		
	if (!$ret)
		return '';
		
	$length = strlen($this->chart_url);
	$this->_trace("Url length: ".$length);
	$this->_trace("Url: ".$this->chart_url);
	
	if ($length > GOOGLE_MAX_URL)
		{
		$this->_error(JText::_('COM_PLOTALOT_ERROR_MAX_DATA'));
		return '';
		}
	
// build the full html img tag and return it

	$image = '<img src="'.$this->chart_url.'" height="'.$ysize.'" width="'.$xsize.'" '.$this->chart_data->chart_css_style.' />';
	return $image;
		
}

}
?>